package cz.muni.fi.pv168.reminder.managers.implementation;

import cz.muni.fi.pv168.reminder.DBUtils;
import cz.muni.fi.pv168.reminder.ServiceFailureException;
import cz.muni.fi.pv168.reminder.managers.TaskManager;
import cz.muni.fi.pv168.reminder.entities.Task;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.sql.DataSource;

/**
 *
 * @author Adic
 */
public class TaskManagerImpl implements TaskManager {

    private DataSource ds;
    public static final Logger logger = Logger.getLogger(TaskManagerImpl.class.getName());

    public void setDataSource(DataSource ds) {
        this.ds = ds;
    }

    private void checkDataSource() {
        if (ds == null) {
            throw new IllegalStateException("DataSource is not set");
        }
    }

    /**
     *
     * @param task
     */
    @Override
    public void createTask(Task task) {
        checkDataSource();
        Connection conn = null;
        PreparedStatement st = null;

        if (task == null) {
            throw new IllegalArgumentException("Task is null");
        }
        if (task.getId() != null) {
            throw new IllegalArgumentException("Task id is already set");
        }

        try {
            conn = ds.getConnection();
            conn.setAutoCommit(false);

            st = conn.prepareStatement("INSERT INTO TASK (name,description,done,deadline) VALUES (?,?,?,?)",
                    Statement.RETURN_GENERATED_KEYS);
            st.setString(1, task.getName());
            st.setString(2, task.getDescription());
            st.setBoolean(3, task.getDone());

            st.setTimestamp(4, new Timestamp(task.getDeadline().getTime()));
            int count = st.executeUpdate();
            DBUtils.checkUpdatesCount(count, task, true);
            Long id = DBUtils.getId(st.getGeneratedKeys());

            task.setId(id);
            conn.commit();
            logger.log(Level.SEVERE, "Task added", task);
        } catch (SQLException ex) {
            String msg = "Error when inserting task into db";
            logger.log(Level.SEVERE, null, ex);
            throw new ServiceFailureException(msg, ex);
        } finally {
            DBUtils.doRollbackQuietly(conn);
            DBUtils.closeQuietly(conn, st);
        }

    }

    /**
     *
     * @param task
     */
    @Override
    public void updateTask(Task task) {
        checkDataSource();
        Connection conn = null;
        PreparedStatement st = null;
        validate(task);

        if (task.getId() == null) {
            throw new IllegalArgumentException("id is null");
        }
        try {

            conn = ds.getConnection();
            conn.setAutoCommit(false);
            st = conn.prepareStatement("UPDATE Task SET name = ?, description = ?, done = ?, deadline = ? WHERE id = ?");
            st.setString(1, task.getName());
            st.setString(2, task.getDescription());
            st.setBoolean(3, task.getDone());
            st.setTimestamp(4, new Timestamp(task.getDeadline().getTime()));
            st.setLong(5, task.getId());

            int count = st.executeUpdate();
            if (count != 1) {
                throw new ServiceFailureException("Internal Error: More rows "
                        + "inserted when trying to insert task " + task);
            }

            DBUtils.checkUpdatesCount(count, task, true);
            conn.commit();
            logger.log(Level.SEVERE, "Task updateded", task);
        } catch (SQLException ex) {
            String msg = "Error when inserting task into db";
            logger.log(Level.SEVERE, null, ex);
            throw new ServiceFailureException(msg, ex);
        } finally {
            DBUtils.doRollbackQuietly(conn);
            DBUtils.closeQuietly(conn, st);
        }
    }

    /**
     *
     * @return
     */
    @Override
    public List<Task> findAllTasks() {
        checkDataSource();
        Connection conn = null;
        PreparedStatement st = null;
        try {
            conn = ds.getConnection();
            st = conn.prepareStatement(
                    "SELECT id,name,description,done,deadline FROM task");
            ResultSet rs = st.executeQuery();

            List<Task> result = new ArrayList<>();
            while (rs.next()) {
                result.add(resultSetToTask(rs));
            }
            return result;

        } catch (SQLException ex) {
            throw new ServiceFailureException("Error when retrieving all persons", ex);
        } finally {
            DBUtils.closeQuietly(conn, st);
        }
    }

    /**
     *
     * @param id
     * @return
     */
    @Override
    public Task findTaskById(Long id) {
        checkDataSource();
        if (id == null) {
            throw new IllegalArgumentException("id is null");
        }
        Connection conn = null;
        PreparedStatement st = null;
        try {
            conn = ds.getConnection();
            st = conn.prepareStatement(
                    "SELECT ID, NAME, DESCRIPTION, DONE, DEADLINE FROM TASK WHERE ID = ?");
            st.setLong(1, id);
            ResultSet rs = st.executeQuery();
            if (rs.next()) {
                Task task = resultSetToTask(rs);

                if (rs.next()) {
                    throw new ServiceFailureException(
                            "Internal error: More entities with the same id found "
                            + "(source id: " + id + ", found " + task + " and " + resultSetToTask(rs));
                }

                return task;
            } else {
                return null;
            }

        } catch (SQLException ex) {
            throw new ServiceFailureException(
                    "Error when retrieving person with id " + id, ex);
        } finally {
            DBUtils.closeQuietly(conn, st);

        }
    }

    @Override
    public void deleteTask(Task task) {
        checkDataSource();
        Connection conn = null;
        PreparedStatement st = null;

        if (task == null) {
            throw new IllegalArgumentException("Task is null");
        }
        if (task.getId() == null) {
            throw new IllegalArgumentException("Task id is null");
        }

        try {
            conn = ds.getConnection();
            conn.setAutoCommit(false);
            st = conn.prepareStatement("DELETE FROM task WHERE id = ?");
            st.setLong(1, task.getId());

            int count = st.executeUpdate();
            DBUtils.checkUpdatesCount(count, task, false);
            conn.commit();
            logger.log(Level.SEVERE, "Task deleted", task);
        } catch (SQLException ex) {
            String msg = "Error when deleting person from db";
            logger.log(Level.SEVERE, null, ex);
            throw new ServiceFailureException(msg, ex);
        } finally {
            DBUtils.doRollbackQuietly(conn);
            DBUtils.closeQuietly(conn, st);
        }
    }

    @Override
    public List<Task> findAllDoneTasks() {
        List<Task> result = new ArrayList<>();
        for (Task t : findAllTasks()) {
            if (t.getDone()) {
                result.add(t);
            }
        }
        return result;
    }

    /**
     *
     * @return unmodifiable List of Tasks over deadline
     */
    @Override
    public List<Task> findAllTasksOverDeadline() {
        List<Task> result = new ArrayList<>();
        for (Task t : findAllTasks()) {
            if(!findAllDoneTasks().contains(t)){
            if (t.getDeadline().before(new Date())) {
                result.add(t);
            }}
        }
        return result;
    }

    private static void validate(Task task) {
        if (task == null) {
            throw new IllegalArgumentException("task is null");
        }

        if (task.getName() == null) {
            throw new IllegalArgumentException("name is null");
        }
        if (task.getDescription() == null) {
            throw new IllegalArgumentException("Description is null");
        }
        if (task.getDone() == null) {
            throw new IllegalArgumentException("Done is null");
        }

        if (task.getDeadline() == null) {
            throw new IllegalArgumentException("DeadLine is null");
        }
    }

    private Long getKey(ResultSet keyRS, Task task) throws ServiceFailureException, SQLException {
        if (keyRS.next()) {
            if (keyRS.getMetaData().getColumnCount() != 1) {
                throw new ServiceFailureException("Internal Error: Generated key"
                        + "retriving failed when trying to insert task " + task
                        + " - wrong key fields count: " + keyRS.getMetaData().getColumnCount());
            }
            Long result = keyRS.getLong(1);
            if (keyRS.next()) {
                throw new ServiceFailureException("Internal Error: Generated key"
                        + "retriving failed when trying to insert task " + task
                        + " - more keys found");
            }
            return result;
        } else {
            throw new ServiceFailureException("Internal Error: Generated key"
                    + "retriving failed when trying to insert task " + task
                    + " - no key found");
        }
    }

    private Task resultSetToTask(ResultSet rs) throws SQLException {
        Task task = new Task();
        task.setId(rs.getLong("id"));
        task.setName(rs.getString("name"));
        task.setDescription(rs.getString("DESCRIPTION"));
        task.setDone(rs.getBoolean("done"));
        task.setDeadline(rs.getTimestamp("deadline"));

        return task;
    }

    @Override
    public List<Task> findAllPendingTasks() {
        checkDataSource();

        Connection conn = null;
        PreparedStatement st = null;
        try {
            conn = ds.getConnection();
            st = conn.prepareStatement(
                    "SELECT ID, NAME, DESCRIPTION, DONE, DEADLINE FROM TASK WHERE DONE = ?");
            st.setBoolean(1, false);
            ResultSet rs = st.executeQuery();

            List<Task> result = new ArrayList<>();
            while (rs.next()) {
                result.add(resultSetToTask(rs));
            }
            return result;

        } catch (SQLException ex) {
            throw new ServiceFailureException(
                    "Error when retrieving done tasks", ex);
        } finally {
            DBUtils.closeQuietly(conn, st);

        }
    }

}
